//
// Created by Yun Zeng on 2018/4/15.
//

#include "platform_oled.h"
#include "master.h"

#include <linux/types.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/types.h>


//
// GPIO14 -> SPI_CS
// GPIO15 -> SPI_CLK
// GPIO16 -> SPI_MISO
// GPIO17 -> SPI_MOSI
//
#define     OLED_CS_PIN     14
#define     OLED_CS_NAME    "spi_chip_select"

#define     OLED_DO_PIN     26
#define     OLED_DO_NAME    "spi_data_output"

#define     OLED_DC_PIN     16
#define     OLED_DC_NAME    "oled_command_or_data"

#define     OLED_CLK_PIN    17
#define     OLED_CLK_NAME   "spi_transfer_clock"

#define     OLED_RST_PIN    0
#define     OLED_RST_NAME   "oled_pin_reset"

#define     OLED_BUFFER_SIZE    8192

static uint8_t oled_display_buffer[OLED_BUFFER_SIZE];

static int oled_gpio_request(void) {
    int result;

    // oled chip select
    result = gpio_request(OLED_CS_PIN, OLED_CS_NAME);
    if (result < 0) {
        printk(MASTER_DEV_NAME ": gpio_request(%d,%s) err\n", OLED_CS_PIN, OLED_CS_NAME);
        return -1;
    }
    gpio_direction_output(OLED_CS_PIN, 1);

    // spi data output
    result = gpio_request(OLED_DO_PIN, OLED_DO_NAME);
    if (result < 0) {
        printk(MASTER_DEV_NAME ": gpio_request(%d,%s) err\n", OLED_DO_PIN, OLED_DO_NAME);
        return -1;
    }
    gpio_direction_output(OLED_DO_PIN, 1);

    // spi clock
    result = gpio_request(OLED_CLK_PIN, OLED_CLK_NAME);
    if (result < 0) {
        printk(MASTER_DEV_NAME ": gpio_request(%d,%s) err\n", OLED_CLK_PIN, OLED_CLK_NAME);
        return -1;
    }
    gpio_direction_output(OLED_CLK_PIN, 1);

    // data or command select
    result = gpio_request(OLED_DC_PIN, OLED_DC_NAME);
    if (result < 0) {
        printk(MASTER_DEV_NAME ": gpio_request(%d,%s) err\n", OLED_DC_PIN, OLED_DC_NAME);
        return -1;
    }
    gpio_direction_output(OLED_DC_PIN, 1);

    // oled pin reset
    result = gpio_request(OLED_RST_PIN, OLED_RST_NAME);
    if (result < 0) {
        printk(MASTER_DEV_NAME ": gpio_request(%d,%s) err\n", OLED_RST_PIN, OLED_RST_NAME);
        return -1;
    }
    gpio_direction_output(OLED_RST_PIN, 1);
    return 0;
}

static int oled_gpio_release(void) {
    gpio_free(OLED_CS_PIN);
    gpio_free(OLED_DO_PIN);
    gpio_free(OLED_DC_PIN);
    gpio_free(OLED_CLK_PIN);
    gpio_free(OLED_RST_PIN);
    return 0;
}

static int oled_command_transfer(uint8_t byte) {
    int i=0;
    gpio_set_value(OLED_DC_PIN, 0);
    ndelay(10);

    for(i=0; i<8; i++) {
        ndelay(2);
        gpio_set_value(OLED_CLK_PIN, 0);
        gpio_set_value(OLED_DO_PIN, ((byte&0x80) ? 1 : 0));
        byte <<= 0x01;
        gpio_set_value(OLED_CLK_PIN, 1);
        ndelay(2);
    }
    return 0;
}

static int oled_data_transfer(uint8_t byte) {
    int i=0;
    gpio_set_value(OLED_DC_PIN, 1);
    ndelay(10);

    for(i=0; i<8; i++) {
        ndelay(2);
        gpio_set_value(OLED_CLK_PIN, 0);
        gpio_set_value(OLED_DO_PIN, ((byte&0x80) ? 1 : 0));
        byte <<= 0x01;
        gpio_set_value(OLED_CLK_PIN, 1);
        ndelay(2);
    }
    return 0;
}

static int oled_display_reset(void) {
    gpio_set_value(OLED_RST_PIN, 0);
    msleep(2);
    gpio_set_value(OLED_RST_PIN, 1);
    msleep(250);
    return 0;
}

static int oled_display_on(void) {
    oled_display_reset();

    gpio_set_value(OLED_CS_PIN, 0);

    // set display off
    oled_command_transfer(0xAF);

    // set re-map
    oled_command_transfer(0xA0);
    oled_command_transfer(0x43);

    // set display start line
    oled_command_transfer(0xA1);
    oled_command_transfer(0x00);

    // set display offset
    oled_command_transfer(0xA2);
    oled_command_transfer(0x00);

    // display normal
    oled_command_transfer(0xA4);

    // set multiplex ratio
    oled_command_transfer(0xA8);
    oled_command_transfer(0x7F);

    // function selection A
    // select external VDD
    oled_command_transfer(0xAB);
    oled_command_transfer(0x00);

    // set contrast
    oled_command_transfer(0x81);
    oled_command_transfer(0x77);

    // set phase length
    oled_command_transfer(0xB1);
    oled_command_transfer(0x31);

    // set font clock dividr -> Oscillator Frequency
    oled_command_transfer(0xB3);
    oled_command_transfer(0xb1);

    // for brightness enhancement
    oled_command_transfer(0xB4);
    oled_command_transfer(0xB5);

    // set second pre-charge period
    oled_command_transfer(0xB6);
    oled_command_transfer(0x0D);

    // set pre-charge voltage
    oled_command_transfer(0xBC);
    oled_command_transfer(0x07);

    oled_command_transfer(0xBE);
    oled_command_transfer(0x07);

    oled_command_transfer(0xD5);
    oled_command_transfer(0x02);

    oled_command_transfer(0xAF);

    gpio_set_value(OLED_CS_PIN, 1);
    return 0;
}

static int oled_display_set(void) {
    int i, j;
    gpio_set_value(OLED_CS_PIN, 0);

    // set column address [start : end]
    oled_command_transfer(0x15);
    oled_command_transfer(0x00);
    oled_command_transfer(0x7F);

    // set row address [start : end]
    oled_command_transfer(0x75);
    oled_command_transfer(0x00);
    oled_command_transfer(0x7F);

    // set column end address
    for(i=0; i<128; i++) {
        for (j=0; j<64; j++) {
            uint8_t value = oled_display_buffer[64*i + 63-j];
            uint8_t newValue = (uint8_t)((value&0x0F) << 0x04 | (value&0xF0) >> 0x04);
            oled_data_transfer(newValue);
        }
    }
    gpio_set_value(OLED_CS_PIN, 1);
    return 0;
}

static int oled_display_off(void) {
    // set display off
    memset(oled_display_buffer, 0x00, sizeof(oled_display_buffer));
    oled_display_set();
    oled_command_transfer(0xAF);
    return 0;
}

int platform_oled_write(uint8_t *buffer, uint16_t len) {
    if (buffer == NULL || len != sizeof(oled_display_buffer)) {
        return -1;
    }

//    printk(MASTER_DEV_NAME ": oled write(%d) data(%02x%02x%02x....)\n",
//           len, buffer[0], buffer[1], buffer[2]);
    memcpy(oled_display_buffer, buffer, len);
    oled_display_set();
    return len;
}


int platform_oled_init(void) {
    // gpio request
    oled_gpio_request();

// 上电显示logo，所有不需要这些设置了
    // display init
//    oled_display_on();
//    memset(oled_display_buffer, 0x00, sizeof(oled_display_buffer));
//    oled_display_set();
    return 0;
}

int platform_oled_exit(void) {
    oled_display_off();
    oled_gpio_release();
    return 0;
}